import { isServer } from './is'
const ieVersion = isServer ? 0 : Number((document as any).documentMode)
const SPECIAL_CHARS_REGEXP = /([\:\-\_]+(.))/g
const MOZ_HACK_REGEXP = /^moz([A-Z])/

export interface ViewportOffsetResult {
    left: number;
    top: number;
    right: number;
    bottom: number;
    rightIncludeBody: number;
    bottomIncludeBody: number;
}

/* istanbul ignore next */
const trim = function(string: string) {
    return (string || '').replace(/^[\s\uFEFF]+|[\s\uFEFF]+$/g, '')
}

/* istanbul ignore next */
const camelCase = function(name: string) {
    return name
        .replace(SPECIAL_CHARS_REGEXP, function(_, separator, letter, offset) {
            return offset ? letter.toUpperCase() : letter
        })
        .replace(MOZ_HACK_REGEXP, 'Moz$1')
}

/* istanbul ignore next */
export function hasClass(el: Element, cls: string) {
    if (!el || !cls) return false
    if (cls.indexOf(' ') !== -1) throw new Error('className should not contain space.')
    if (el.classList) {
        return el.classList.contains(cls)
    } else {
        return ` ${el.className} `.indexOf(` ${cls} `) > -1
    }
}

/* istanbul ignore next */
export function addClass(el: Element, cls: string) {
    if (!el) return
    let curClass = el.className
    const classes = (cls || '').split(' ')

    for (let i = 0, j = classes.length; i < j; i++) {
        const clsName = classes[i]
        if (!clsName) continue

        if (el.classList) {
            el.classList.add(clsName)
        } else if (!hasClass(el, clsName)) {
            curClass += ` ${clsName}`
        }
    }
    if (!el.classList) {
        el.className = curClass
    }
}

/* istanbul ignore next */
export function removeClass(el: Element, cls: string) {
    if (!el || !cls) return
    const classes = cls.split(' ')
    let curClass = ` ${el.className} `

    for (let i = 0, j = classes.length; i < j; i++) {
        const clsName = classes[i]
        if (!clsName) continue

        if (el.classList) {
            el.classList.remove(clsName)
        } else if (hasClass(el, clsName)) {
            curClass = curClass.replace(` ${clsName} `, ' ')
        }
    }
    if (!el.classList) {
        el.className = trim(curClass)
    }
}

export function getBoundingClientRect(element: Element): DOMRect | number {
    if (!element || !element.getBoundingClientRect) {
        return 0
    }
    return element.getBoundingClientRect()
}

/**
 * 获取当前元素的left、top偏移
 *   left：元素最左侧距离文档左侧的距离
 *   top:元素最顶端距离文档顶端的距离
 *   right:元素最右侧距离文档右侧的距离
 *   bottom：元素最底端距离文档底端的距离
 *   rightIncludeBody：元素最左侧距离文档右侧的距离
 *   bottomIncludeBody：元素最底端距离文档最底部的距离
 *
 * @description:
 */
export function getViewportOffset(element: Element): ViewportOffsetResult {
    const doc = document.documentElement

    const docScrollLeft = doc.scrollLeft
    const docScrollTop = doc.scrollTop
    const docClientLeft = doc.clientLeft
    const docClientTop = doc.clientTop

    const { pageXOffset } = window
    const { pageYOffset } = window

    const box = getBoundingClientRect(element)

    const { left: retLeft, top: rectTop, width: rectWidth, height: rectHeight } = box as DOMRect

    const scrollLeft = (pageXOffset || docScrollLeft) - (docClientLeft || 0)
    const scrollTop = (pageYOffset || docScrollTop) - (docClientTop || 0)
    const offsetLeft = retLeft + pageXOffset
    const offsetTop = rectTop + pageYOffset

    const left = offsetLeft - scrollLeft
    const top = offsetTop - scrollTop

    const { clientWidth } = window.document.documentElement
    const { clientHeight } = window.document.documentElement
    return {
        left: left,
        top: top,
        right: clientWidth - rectWidth - left,
        bottom: clientHeight - rectHeight - top,
        rightIncludeBody: clientWidth - left,
        bottomIncludeBody: clientHeight - top
    }
}

/* istanbul ignore next */
export const on = function(
    element: HTMLElement | Document | Window,
    event: string,
    handler: EventListenerOrEventListenerObject,
    use = false // true表示捕获，默认false表示冒泡
): void {
    if (element && event && handler) {
        element.addEventListener(event, handler, use)
    }
}

/* istanbul ignore next */
export const off = function(
    element: HTMLElement | Document | Window,
    event: string,
    handler: any,
    use = false // true表示捕获，默认false表示冒泡
): void {
    if (element && event && handler) {
        element.removeEventListener(event, handler, use)
    }
}

/* istanbul ignore next */
export const getStyle =
    ieVersion < 9
        ? function(element: Element | any, styleName: string) {
            if (isServer) return
            if (!element || !styleName) return null
            styleName = camelCase(styleName)
            if (styleName === 'float') {
                styleName = 'styleFloat'
            }
            try {
                switch (styleName) {
                case 'opacity':
                    try {
                        return element.filters.item('alpha').opacity / 100
                    } catch (e) {
                        return 1.0
                    }
                default:
                    return element.style[styleName] || element.currentStyle
                        ? element.currentStyle[styleName]
                        : null
                }
            } catch (e) {
                return element.style[styleName]
            }
        }
        : function(element: Element | any, styleName: string) {
            if (isServer) return
            if (!element || !styleName) return null
            styleName = camelCase(styleName)
            if (styleName === 'float') {
                styleName = 'cssFloat'
            }
            try {
                const computed = (document as any).defaultView.getComputedStyle(element, '')
                return element.style[styleName] || computed ? computed[styleName] : null
            } catch (e) {
                return element.style[styleName]
            }
        }

/* istanbul ignore next */
export function setStyle(element: Element | any, styleName: any, value: any) {
    if (!element || !styleName) return

    if (typeof styleName === 'object') {
        for (const prop in styleName) {
            if (Object.prototype.hasOwnProperty.call(styleName, prop)) {
                setStyle(element, prop, styleName[prop])
            }
        }
    } else {
        styleName = camelCase(styleName)
        if (styleName === 'opacity' && ieVersion < 9) {
            element.style.filter = isNaN(value) ? '' : `alpha(opacity=${value * 100})`
        } else {
            element.style[styleName] = value
        }
    }
}

/* istanbul ignore next */
export const isScroll = (el: Element, vertical: any) => {
    if (isServer) return

    const determinedDirection = vertical !== null || vertical !== undefined
    const overflow = determinedDirection
        ? vertical
            ? getStyle(el, 'overflow-y')
            : getStyle(el, 'overflow-x')
        : getStyle(el, 'overflow')

    return overflow.match(/(scroll|auto)/)
}

/* istanbul ignore next */
export const getScrollContainer = (el: Element, vertical?: any) => {
    if (isServer) return

    let parent: any = el
    while (parent) {
        if ([window, document, document.documentElement].includes(parent)) {
            return window
        }
        if (isScroll(parent, vertical)) {
            return parent
        }
        parent = parent.parentNode
    }

    return parent
}

/* istanbul ignore next */
export const isInContainer = (el: Element, container: any) => {
    if (isServer || !el || !container) return false

    const elRect = el.getBoundingClientRect()
    let containerRect

    if ([window, document, document.documentElement, null, undefined].includes(container)) {
        containerRect = {
            top: 0,
            right: window.innerWidth,
            bottom: window.innerHeight,
            left: 0
        }
    } else {
        containerRect = container.getBoundingClientRect()
    }

    return (
        elRect.top < containerRect.bottom &&
        elRect.bottom > containerRect.top &&
        elRect.right > containerRect.left &&
        elRect.left < containerRect.right
    )
}
